﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Resources;
using System.Text.RegularExpressions;

using System.Xml;

namespace LocalisationResourceManager
{
    /// <summary>
    /// The public class of this component.
    /// </summary>
    public class Lrm
    {
        // public options.
        public const int MaxLengthAnExcelCellCanHold = 911;
        private static readonly Hashtable ResxCache = new Hashtable();
        internal static ResourceRepository ResourceRepository;
        public static bool GenerateRes;
        public static bool RebuildAccessKey;
        public static string ResourceRepositoryPath = string.Empty;
        public static bool WriteUniqueResourceOnly;
        public static event PerformRuleEvaluationHandler PerformRuleEvaluation;

        private static XmlDocument ReadXmlDocument(string fileName)
        {
            var doc = new XmlDocument();

            string file = GetFileContents(fileName);

            try
            {
                doc.LoadXml(file);
            }
            catch
            {
                return null;
            }

            return doc;
        }

        /// <summary>
        /// Generate language-specific resource files for .NET projects.
        /// </summary>
        /// <param name="projNames">the projects that need to add the resource files</param>
        /// <param name="requireToSupportCultures">the targets cultures</param>
        public static void GenerateResourceFileForProjects(string[] projNames,
                                                           string[] requireToSupportCultures)
        {
            ResourceRepository = new ResourceRepository();

            // reads the excel files
            Hashtable cultures = ResourceRepository.Initialize();

            foreach (string cult in requireToSupportCultures)
                cultures[cult] = cult;

            // generate the language-specific resource files for each project.
            foreach (string projName in projNames)
                GenerateResourceFileForProject(projName, cultures.Values);

            // updates the excel files.
            ResourceRepository.Save();
        }

        private static bool IsFileNameContainsCulture(string fileName)
        {
            if (Path.GetExtension(fileName) != ".resx")
                return false;

            string fileNo = Path.GetFileNameWithoutExtension(fileName);

            int l = fileNo.LastIndexOf(".");

            if (l < 0)
                return false;

            return TranslationSheet.IsSupportdCultureInfo(CultureTypes.AllCultures, fileNo.Substring(l + 1));
        }

        //  generate language-specific resource files for a project for all the target cultures.
        private static void GenerateResourceFileForProject(string projName, ICollection cultures)
        {
            Debug.WriteLine(projName);

            const string pathTag = "Include";

            // only csproj and vbproj files are supported.
            if (!File.Exists(projName)
                || (string.Compare(Path.GetExtension(projName), ".csproj", true) == 0
                    && string.Compare(Path.GetExtension(projName), ".vbproj", true) == 0)
                || new FileInfo(projName).Length == 0)
                return;

            // set the file to writable.
            SetFileWritable(projName);

            XmlDocument xmlDoc = ReadXmlDocument(projName);

            if (xmlDoc == null)
                return;

            string relPathStr;

            string fileNodesPath;
            if (string.Compare(Path.GetExtension(projName), ".csproj", true) == 0)
                fileNodesPath = "//ns:Project/ns:ItemGroup/ns:EmbeddedResource";
            else if (string.Compare(Path.GetExtension(projName), ".vbproj", true) == 0)
                fileNodesPath = "//VisualStudioProject/VisualBasic/Files/Include/File";
            else
                return;

            //Instantiate an XmlNamespaceManager object. 
            var xmlnsManager = new XmlNamespaceManager(xmlDoc.NameTable);

            //Add the namespaces used in books.xml to the XmlNamespaceManager.
            xmlnsManager.AddNamespace("ns", "http://schemas.microsoft.com/developer/msbuild/2003");
            
            XmlNodeList fileNodes = xmlDoc.SelectNodes(fileNodesPath, xmlnsManager);

            var allFileNodes = new Hashtable();

            bool someNodesAdded = false;

            Console.WriteLine("Detected Resources");

            foreach (XmlNode node in fileNodes)
            {
                Console.WriteLine(node.Attributes.GetNamedItem(pathTag).InnerText);
                allFileNodes.Add(node.Attributes.GetNamedItem(pathTag).InnerText, 1);
            }

            XmlNode fileNodesRoot = xmlDoc.SelectNodes(fileNodesPath, xmlnsManager).Item(0).ParentNode;

            foreach (XmlNode fileNode in fileNodes)
            {
                if (fileNode.Attributes.GetNamedItem(pathTag) != null)
                {
                    relPathStr = fileNode.Attributes.GetNamedItem(pathTag).InnerText;

                    // skip any language-specific resource files.
                    if (IsFileNameContainsCulture(relPathStr))
                        continue;

                    if (relPathStr.ToLower().IndexOf(".resx") >= 0)
                    {
                        if (relPathStr.ToLower().EndsWith(".aspx.resx"))
                            continue;

                        if (!File.Exists(Path.Combine(Path.GetDirectoryName(projName), relPathStr)))
                            continue;

                        XmlNode cloned;

                        foreach (string cult in cultures)
                        {
                            // new node for the language-specific resource file.
                            cloned = fileNode.Clone();
                            string newResourceFileName = relPathStr.Insert(relPathStr.LastIndexOf("."), "." + cult);
                            cloned.Attributes.GetNamedItem(pathTag).InnerText = newResourceFileName;

                            if (HasGeneratedLanguageSpecificResourceFile(Path.GetDirectoryName(projName), relPathStr, cult, newResourceFileName))
                            {
                                // only add it if not existing
                                if (!allFileNodes.ContainsKey(cloned.Attributes.GetNamedItem(pathTag).InnerText))
                                {
                                    fileNodesRoot.AppendChild(cloned);
                                    someNodesAdded = true;
                                }
                            }
                        }
                    }
                }
            }

            // save the project file.
            if (someNodesAdded)
                xmlDoc.Save(projName);
        }

        // Try to generate the language-specific resource file.
        private static bool HasGeneratedLanguageSpecificResourceFile(string folder, string sourcePath, string culture, string outPath)
        {
            string languageSpecificResourceFilePath = Path.Combine(folder, outPath);
            string defaultResourceFile = Path.Combine(folder, sourcePath);
            var translations = new Dictionary<String, String>();

            if (File.Exists(languageSpecificResourceFilePath))
            {
                Console.Write("Checking the resource file: {0}", languageSpecificResourceFilePath);

                if (File.GetLastWriteTime(languageSpecificResourceFilePath) > File.GetLastWriteTime(defaultResourceFile) &&
                    File.GetLastWriteTime(languageSpecificResourceFilePath) > ResourceRepository.CultureLastModifiedTime(culture))
                {
                    Console.WriteLine("Resource file {0} regeneration not required", languageSpecificResourceFilePath);
                    return true;
                }
                Console.WriteLine("Resource file {0} regeneration required", languageSpecificResourceFilePath);
            }

            // cache nodes.
            if (!ResxCache.ContainsKey(defaultResourceFile))
            {
                XmlDocument doc = ReadXmlDocument(defaultResourceFile);

                ResxCache[defaultResourceFile] = new[] {doc.SelectNodes("/root/data")};
            }

            XmlNodeList dataNodes = ((XmlNodeList[]) ResxCache[defaultResourceFile])[0];

            foreach (XmlNode node in dataNodes)
            {
                // not translate if type or mimetype is not null.
                if (node.Attributes.GetNamedItem("type") != null ||
                    node.Attributes.GetNamedItem("mimetype") != null)
                    continue;

                if (node.FirstChild == null)
                    continue;

                string name = node.Attributes.GetNamedItem("name").InnerText;
                //string word = node.FirstChild.InnerText;
                string word = node.ChildNodes.Item(1).InnerText;

                if (word == string.Empty)
                    continue;

                if (NeedsToTranslate(name, word, defaultResourceFile))
                {
                    ResourceRow entry = ResourceRepository[culture][word, defaultResourceFile, name];
                    if (word != entry.LocalizedValue
                        && ReplaceAltKey(word) != entry.LocalizedValue
                        && entry.LocalizedValue != string.Empty)
                    {
                        translations.Add(name,entry.LocalizedValue);
                    }
                }
            }

            if(translations.Count > 0)
            {
                SetFileWritable(languageSpecificResourceFilePath);

                using(var resXResourceWriter = new ResXResourceWriter(languageSpecificResourceFilePath))
                {
                    foreach (KeyValuePair<string, string> pair in translations)
                    {
                        resXResourceWriter.AddResource(pair.Key, pair.Value);
                    }    
                }
            }

            return false;
        }

        // change a file's attribute to writable.
        internal static void SetFileWritable(string fileName)
        {
            if (File.Exists(fileName))
            {
                if (((File.GetAttributes(fileName) & FileAttributes.ReadOnly) > 0))
                    File.SetAttributes(fileName, File.GetAttributes(fileName) ^ FileAttributes.ReadOnly);
            }
        }


        private static bool NeedsToTranslate(string name, string invariantString, string path)
        {
            if (string.IsNullOrEmpty(name))
                return false;

            if (string.IsNullOrEmpty(invariantString))
                return false;

            if (name.StartsWith(">>"))
                return false;

            if (null != PerformRuleEvaluation)
            {
                var e = new PerformRuleEvaluationArgs {InvariantString = invariantString, Name = name};

                // fire the PerformRuleEvalution event and let the EXE handles it.
                foreach (Delegate d in PerformRuleEvaluation.GetInvocationList())
                {
                    d.DynamicInvoke(new object[] {null, e});
                    if (e.Handled)
                    {
                        if (!e.NeedToBeTranslated)
                        {
                            ResourceRow entry = ResourceRepository[e.Cause][invariantString, path, name];
                            return false;
                        }
                        break;
                    }
                }
            }

            return true;
        }

        //  reconstruct the string by moving the access key to the end of the string and 
        //  putting parentheses around it.
        internal static string ReplaceAltKey(string s)
        {
            if (!RebuildAccessKey)
                return s;

            var digitregex = new Regex("(?<altkey>&[0-9a-zA-Z])");

            MatchCollection ms = digitregex.Matches(s);
            if (ms.Count == 1)
            {
                string found = ms[0].Groups["altkey"].Value;
                return s.Replace("&", "") + " (" + found + ")";
            }
            return s;
        }

        private static string GetFileContents(string fileName)
        {
            var sr = new StreamReader(fileName);

            string ret = sr.ReadToEnd();

            sr.Close();

            return ret;
        }
    }

    public delegate void PerformRuleEvaluationHandler(object sender, PerformRuleEvaluationArgs e);

    public class PerformRuleEvaluationArgs
    {
        private string _cause = string.Empty;
        private string _invariantString = string.Empty;
        private string _name = string.Empty;

        internal PerformRuleEvaluationArgs()
        {
        }

        public bool Handled { get; set; }

        public string Cause
        {
            get { return _cause; }
            set { _cause = value; }
        }

        public bool NeedToBeTranslated { get; set; }

        public string InvariantString
        {
            get { return _invariantString; }
            set { _invariantString = value; }
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    }
}
